package se.webbzon.boltzmann.battle.battleaction;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import se.webbzon.boltzmann.battle.object.npc.BattleNPC;
import se.webbzon.boltzmann.battle.property.BattleProperty;
import se.webbzon.boltzmann.effect.CharacterDefeatingEffect;
import se.webbzon.boltzmann.npc.NPCDuty;
import se.webbzon.oschi01.worldarea.WorldArea;

public abstract class BattleAction {
	
	// The world area associated with this battle action
	private final WorldArea world;
	
	// The caster of this battle action
	private final BattleNPC caster;
	
	// The duty used by the caster before the action was started
	private NPCDuty casterDuty;
	
	// The targets of this battle action
	private final List<BattleNPC> targets;
	
	// The duty used by the targets before the action was started
	private final Map<BattleNPC,NPCDuty> targetDuties;
	
	// A collection of all listeners to this battle action
	private final Collection<BattleActionListener> listeners; 
	
	/*============================================================
	Constructors
	============================================================*/
	
	/** Creates a new abstract battle action. **/
	public BattleAction(WorldArea world, BattleNPC caster, List<BattleNPC> targets) {
		this.world = world;
		this.caster = caster;
		this.targets = targets;
		this.casterDuty = caster.getDuty();
		targetDuties = new HashMap<BattleNPC,NPCDuty>();
		for (BattleNPC npc : targets)
			targetDuties.put(npc,npc.getDuty());
		listeners = new ArrayList<BattleActionListener>();
	}
	
	/*============================================================
	Public Methods
	============================================================*/
	
	/** This method is invoked when the battle action begins. **/
	public abstract void onBegin();
	
	/** This method is invoked when the battle action ends. **/
	public abstract void onEnd();
	
	/** This method is invoked when the given target absorbs a battle action. **/
	public abstract void onAbsorb(BattleNPC target);
	
	/** This method is invoked when the given target takes damage. **/
	public abstract void onDamage(BattleNPC target);
	
	/** This method is invoked when the given target is healed. **/
	public abstract void onHeal(BattleNPC target);
	
	/** Adds a listener to this battle action. **/
	public boolean addListener(BattleActionListener listener) {
		return listeners.add(listener);
	}
	
	/** Removes a listener from this battle action. **/
	public boolean removeListener(BattleActionListener listener) {
		return listeners.remove(listener);
	}
	
	/** Clears all listeners from this battle action. **/
	public void clearListener(BattleActionListener listener) {
		listeners.clear();
	}
	
	/** Damages the target for a given amount. **/
	public void damage(BattleNPC target, int damageAmount) {
		if (damageAmount <= 0)
			onAbsorb(target);
		else {
			BattleProperty property = target.getProperty();
			int health = property.getHealth();

			if (health <= damageAmount) {
				property.setHealth(0);
				onDefeat(target);
			} else {
				property.setHealth(health - damageAmount);
				onDamage(target);
			}
		}
	}
	
	/** Heals the target for a given amount. **/
	public void heal(BattleNPC target, int healAmount) {
		if (healAmount <= 0)
			onAbsorb(target);
		else {
			BattleProperty property = target.getProperty();
			int health = property.getHealth();
			int maxHealth = property.getMaxHealth();
			
			if (health + healAmount >= maxHealth)
				property.setHealth(maxHealth);
			else
				property.setHealth(health + healAmount);
				
			onHeal(target);	
		}
		
	}
	
	/** Returns the world area associated with this battle action. **/
	public WorldArea getWorld() {
		return world;
	}
	
	/** Returns the caster of this battle action. **/
	public BattleNPC getCaster() {
		return caster;
	}
	
	/** Returns the targets of this battle action. **/
	public List<BattleNPC> getTargets() {
		return Collections.unmodifiableList(targets);
	}
	
	/** Starts the battle action. **/
	public void start() {
		onBegin();
		
		for (BattleActionListener listener : listeners)
			listener.onBegin(this);
	}
	
	/** Terminates the battle action. This action must be called from the
	 * battle action implementation upon action termination. **/
	public void end() {
		onEnd();
		
		// Reset the old duties
		caster.assignDuty(casterDuty);
		Set<Entry<BattleNPC,NPCDuty>> entries = targetDuties.entrySet();
		for (Entry<BattleNPC,NPCDuty> entry : entries)
			entry.getKey().assignDuty(entry.getValue());
		
		for (BattleActionListener listener : listeners)
			listener.onEnd(this);
	}
	
	/** Sets the reset duty for the given target. This duty will be
	 * used after the battle action is complete. **/
	public void setResetDuty(BattleNPC target, NPCDuty resetDuty) {
		if (target == caster)
			casterDuty = resetDuty;
		else if (targetDuties.containsKey(target))
			targetDuties.put(target, resetDuty);
		else
			target.assignDuty(resetDuty);
	}
	
	/** Returns the reset duty for the given target. The returned
	 * duty will be used after the battle action is complete. **/
	public NPCDuty getResetDuty(BattleNPC target) {
		if (target == caster)
			return casterDuty;
		else {
			NPCDuty duty = targetDuties.get(target);
			if (duty == null)
				return target.getDuty();
			else
				return duty;
		}
	}
	
	/*============================================================
	Private Methods
	============================================================*/
	
	/** This method is invoked when the given target is defeated. **/
	private void onDefeat(BattleNPC target) {
		new CharacterDefeatingEffect(target,60).launch(getWorld());
	}

}
